/***************************************************************************\
 * The Mattise Kernel														*
 * Copyright 2007 Matthew Iselin											*
 * Licensed under the GPL													*
 *																			*
 * gdt.cc																	*
 *																			*
 * Installs the GDT for Mattise												*
 *																			*
\***************************************************************************/

// tasks
#include <task.h>
#include <iostream>

// global TSS
TSS_t global_tss;

// gdt entry type
struct gdt_entry
{
	unsigned short limit_low;
	unsigned short base_low;
	unsigned char base_middle;
	unsigned char access;
	unsigned char granularity;
	unsigned char base_high;
} __attribute__((packed));

/* Special pointer which includes the limit: The max bytes
*  taken up by the GDT, minus 1. Again, this NEEDS to be packed */
struct gdt_ptr
{
	unsigned short limit;
	unsigned int base;
} __attribute__((packed));

/* Our GDT, with 3 entries */
union gdt_type {
	struct gdt_entry gdtent;
	unsigned long long gdtdat;
};
union gdt_type gdt[15];
extern "C" {

	// for gdtr
	struct gdt_ptr gp;
	
	// sets up new segment registers, loads gdtr
	extern void gdt_flush();
}

// the kernel stack
char kstack[1024];
char iobitmap[0xFFFF];

// installs the TSS
void setup_tss()
{
	// default to allowing all ports
	for( int i = 0; i < 0xFFFF; i += sizeof( unsigned long long ) )
		*((unsigned long long*) &iobitmap[i]) = 0;

	// setup the settings (change these if necessary)
	global_tss.esp0 = (unsigned int) (kstack + 1024);
	global_tss.ss0 = global_tss.ss = 0x10;
	global_tss.ldt = 0;
	global_tss.iomap = (uint32_t) iobitmap;
}

/* Setup a descriptor in the Global Descriptor Table */
void gdt_set_gate(int num, unsigned long base, unsigned long limit, unsigned char access, unsigned char gran)
{
	/* Setup the descriptor base address */
	gdt[num].gdtent.base_low = (base & 0xFFFF);
	gdt[num].gdtent.base_middle = (base >> 16) & 0xFF;
	gdt[num].gdtent.base_high = (base >> 24) & 0xFF;

	/* Setup the descriptor limits */
	gdt[num].gdtent.limit_low = (limit & 0xFFFF);
	gdt[num].gdtent.granularity = ((limit >> 16) & 0x0F);

	/* Finally, set up the granularity and access flags */
	gdt[num].gdtent.granularity |= (gran & 0xF0);
	gdt[num].gdtent.access = access;
}

/* Should be called by main. This will setup the special GDT
*  pointer, set up the first 3 entries in our GDT, and then
*  finally call gdt_flush() in our assembler file in order
*  to tell the processor where the new GDT is and update the
*  new segment registers */
void gdt_install()
{
	/* Setup the GDT pointer and limit */
	gp.limit = (sizeof(struct gdt_entry) * 7) - 1;
	gp.base = (unsigned int) gdt;

	// 0x0: Our NULL descriptor
	gdt_set_gate(0, 0, 0, 0, 0);

	// 0x08: Code segment (32-bit)
	gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF);

	// 0x10: Data segment (32-bit)
	gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF);
	
	// 0x18: 32-bit RPL3 code segment
	gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xCF);
	
	// 0x20: 32-bit RPL3 data segment
	gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xCF);
	
	// 0x28: the TSS - the limit is equal to the size of the tss PLUS the size of the i/o bitmap
	gdt_set_gate(5, (unsigned long) &global_tss, sizeof( TSS_t ) + 0xFFFF, 0x89, 0x0F );
	
	// setup the tss
	setup_tss();

	// flush gdt and install changes
	gdt_flush();
	
	// load the task register and the ldt
	__asm__ __volatile__ ( "ltr %%ax" : : "a" ( 5 * sizeof( struct gdt_entry ) ) );
}
